package qe.spider.trade.ths_lanzi;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.csv.CsvData;
import cn.hutool.core.text.csv.CsvReadConfig;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import com.google.gson.Gson;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import qe.spider.MainSelect;
import qe.spider.cfg.Factor;
import qe.spider.cfg.Others;
import qe.spider.cfg.TradeConfig;
import qe.spider.cfg.TradeTHSCfg;
import qe.spider.trade.StockTrade;
import qe.spider.trade.vo.*;
import qe.spider.vo.Stock;
import qe.spider.vo.StockList;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Data
public class StockTradeTHSLanZi implements StockTrade {
    public static String LanZiAPathBase = "D:\\Tools\\THSTrade\\LanZi\\";
    public static List<String> reportDirs = List.of("D:\\Tools\\ThsQstudio\\log\\algo_file\\report\\", "D:\\同花顺\\智能交易个人版\\log\\algo_file\\report");
    public static Map<String, StockTradeTHSLanZi> accounts = new ConcurrentHashMap<>();

    private static Executor executor = ThreadUtil.newFixedExecutor(100, "同花顺篮子交易", false);//Executors.newFixedThreadPool(100, new NamedThreadFactory("华鑫交易",false));

    static {
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    accounts.values().forEach(t -> {
                        t.queryFund();
                        t.queryOrder();
                        t.queryPositions();
                    });
                } catch (Exception e) {
                    log.error("同花顺查询资金和持仓错误", e);
                }
            }
        }, 10, 1000 * 10);
    }

    public static synchronized void initTHSTrade() {
        List<TradeTHSCfg> cfgs = Optional.ofNullable(MainSelect.factor.getOthers()).map(Others::getTradeConfig).map(TradeConfig::getTradeTHSCfgList).orElse(new ArrayList<>());
        if (cfgs.isEmpty()) return;
        Map<String, StockTradeTHSLanZi> newAcc = cfgs.stream().filter(c -> !accounts.containsKey(c.getUserId())).map(StockTradeTHSLanZi::new).collect(Collectors.toConcurrentMap(StockTradeTHSLanZi::getUserId, each -> each, (value1, value2) -> value1));
        if (newAcc.isEmpty()) return;
        accounts.putAll(newAcc);

    }

    private StockTradeTHSLanZi(TradeTHSCfg cfg) {
        this.cfg = cfg;
        this.userId = cfg.getUserId();
        this.userName = cfg.getUserName();
    }

    private String userName;
    private String userId;
    private TradeTHSCfg cfg;

    private Funds funds;
    private List<Order> orders = new ArrayList<>();
    private List<Position> positions = new ArrayList<>();

    public StockTradeTHSLanZi(String userId) {
        this.userId = userId;
    }

    public Funds queryFund() {
        File csv = FileUtil.file(LanZiAPathBase + "资金明细\\资金" + DateUtil.today().replaceAll("-", "") + ".csv");
        if (!csv.exists()) return funds;    //文件里保存了多个账户
        CsvData data = CsvUtil.getReader(CsvReadConfig.defaultConfig().setContainsHeader(true)).read(csv, Charset.forName("gb2312"));
        List<Funds> allFunds = data.getRows().stream().map(row -> Funds.builder().userId(row.getByName("资金账户").replaceAll("'", ""))
//                        .userName(row.getByName("账户别名").trim())
                        .total(NumberUtil.toBigDecimal(row.getByName("总资产")))
//                    .total(NumberUtil.toBigDecimal(row.getByName("资金余额")))
                        .available(NumberUtil.toBigDecimal(row.getByName("可用余额")))
                        .frozen(NumberUtil.toBigDecimal(row.getByName("冻结资产")))
                        .build()
        ).toList();
        allFunds.forEach(nf -> Optional.ofNullable(accounts.get(nf.getUserId())).map(s -> s.funds = nf));
        return Optional.ofNullable(accounts.get(userId)).map(t -> funds).orElse(null);
    }

    public List<Order> queryOrder() {
        File csv = FileUtil.file(LanZiAPathBase + "委托明细\\委托" + DateUtil.today().replaceAll("-", "") + ".csv");
        if (!csv.exists()) return orders == null ? orders = new ArrayList<>() : orders;
        CsvData data = CsvUtil.getReader(CsvReadConfig.defaultConfig().setContainsHeader(true)).read(csv, Charset.forName("gb2312"));
        List<Order> allOrders = data.getRows().stream().map(r ->
                Order.builder().userId(r.getByName("资金账户").replaceAll("'", ""))
                        .orderType(orderType(r.getByName("操作")))
                        .localID(r.getByName("合同编号"))
                        .sysID(r.getByName("合同编号"))
                        .code(r.getByName("证券代码").replaceAll("'", ""))
                        .price(NumberUtil.toBigDecimal(r.getByName("委托价格")))
                        .volume(NumberUtil.toBigDecimal(r.getByName("委托数量")))
                        .amount(NumberUtil.toBigDecimal(r.getByName("委托价格")).multiply(NumberUtil.toBigDecimal(r.getByName("委托数量"))))
                        .status(orderStatus(r.getByName("订单状态")))
                        .orderTime(DateUtil.parse(r.getByName("委托日期") + r.getByName("委托时间"), "yyyyMMddHH:mm:ss"))
                        .opTime(DateUtil.parse(r.getByName("委托日期") + r.getByName("委托时间"), "yyyyMMddHH:mm:ss"))
                        .build()
        ).toList();
        allOrders.forEach(nf -> Optional.ofNullable(accounts.get(nf.getUserId())).map(s -> s.updateOrder(nf)));
        return Optional.ofNullable(accounts.get(userId)).map(t -> t.orders).orElse(new ArrayList<>());
    }

    private OrderType orderType(String t) {
        if ("买入".equals(t)) return OrderType.Buy;
        else if ("卖出".equals(t)) return OrderType.Sell;
        else return OrderType.Other;
    }

    private OrderStatus orderStatus(String value) {
        if (value.equals("未成交")) return OrderStatus.Received;
        else if (value.equals("全部成交")) return OrderStatus.Success;
        else if (value.equals("部分成交")) return OrderStatus.Partial;
        else if (value.equals("已撤单")) return OrderStatus.Canceled;
        else return OrderStatus.Unknown;
    }

    private Boolean updateOrder(Order newOrder) {
        Collection<Order> exists = orders.stream().filter(o -> o.getUserId().equals(newOrder.getUserId()) && o.getCode().equals(newOrder.getCode()) && o.getAmount().compareTo(newOrder.getAmount()) == 0 && o.getPrice().compareTo(newOrder.getPrice()) == 0).collect(Collectors.toSet());
        if (exists.isEmpty()) return orders.add(newOrder);
        else {
            exists.forEach(o -> {
                o.setSysID(newOrder.getSysID());
                o.setStatus(newOrder.getStatus());
                o.setOpTime(newOrder.getOpTime());
            });
            return true;
        }
    }

    public List<Position> queryPositions() {
        File csv = FileUtil.file(LanZiAPathBase + "持仓明细\\持仓" + DateUtil.today().replaceAll("-", "") + ".csv");
        if (!csv.exists()) return positions == null ? positions = new ArrayList<>() : positions;
        CsvData data = CsvUtil.getReader(CsvReadConfig.defaultConfig().setContainsHeader(true)).read(csv, Charset.forName("gb2312"));
        List<Position> allPositions = data.getRows().stream().map(r ->
                Position.builder().userId(r.getByName("资金账户").replaceAll("'", ""))
                        .code(r.getByName("证券代码").replaceAll("'", ""))
                        .name(r.getByName("证券名称"))
                        .cost(NumberUtil.toBigDecimal(r.getByName("成本价")))
                        .position(NumberUtil.toBigDecimal(r.getByName("股票余额")).intValue())
                        .available(NumberUtil.toBigDecimal(r.getByName("可用数量")).intValue())
                        .date(DateUtil.today())
                        .build()
        ).toList();
//        log.info("this.pos={},accounts.get(userId)={}，accounts.get(userId).positions={}", positions, accounts.get(userId), accounts.get(userId).positions);
        accounts.get(userId).positions = allPositions.stream().filter(p -> p.getUserId().equals(userId)).toList();
        return Optional.ofNullable(accounts.get(userId)).map(t -> t.positions).orElse(new ArrayList<>());
    }

    public Boolean enableBuy(OrderStatus status) {
        return status == OrderStatus.Canceled || status == OrderStatus.Rejected;
    }

    public Boolean buy(Stock stock) {
        String uuid = IdUtil.fastSimpleUUID();
        if (!stock.currentPrice().isPresent()) {
            log.error("没有" + stock.getStock_code() + "的价格");
            return false;
        }
        if (funds == null) queryFund();
        if (funds == null) {
            log.error("{}({})【同花顺委托买入】{}----未查到可用资金余额", userName, userId, stock.codeName());
            return false;
        }
        if (orders == null || orders.isEmpty()) queryOrder();
        if (positions == null || positions.isEmpty()) queryPositions();
        Integer maxBuyCntPerDay = cfg.getMaxBuyCntPerDay() != null ? cfg.getMaxBuyCntPerDay() : Optional.ofNullable(MainSelect.factor.getOthers()).map(Others::getTradeConfig).map(TradeConfig::getMaxBuyCntPerDay).orElse(5);
        Collection<Order> todayOrders = orders.stream().filter(o -> o.getUserId().equals(userId) && o.getOrderType() == OrderType.Buy && DateUtil.isSameDay(o.getOrderTime(), new Date()) && !enableBuy(o.getStatus())).collect(Collectors.toSet());
        Collection<Position> holds = positions.stream().filter(p -> p.getUserId().equals(userId) && p.getPosition() > 0).toList();
        Set<String> doings = Stream.of(todayOrders.stream().map(Order::getCode).toList(), holds.stream().map(Position::getCode).toList()).flatMap(Collection::stream).collect(Collectors.toSet());
        if (doings.size() > maxBuyCntPerDay) {
            log.error("{}({})【同花顺委托买入】{}----今日买入委托{}或持仓{}总量：{} 已达上限：{}", userName, userId, stock.codeName(), todayOrders.size(), holds.size(), doings.size(), maxBuyCntPerDay);
            return false;
        }
        BigDecimal cash = funds == null ? new BigDecimal("40000") : (NumberUtil.isGreater(funds.getTotal().divide(BigDecimal.valueOf(maxBuyCntPerDay), 2, RoundingMode.HALF_UP), funds.getAvailable()) ? funds.getAvailable() : funds.getTotal().divide(BigDecimal.valueOf(maxBuyCntPerDay), 2, RoundingMode.HALF_UP));
        BigDecimal price = stock.currentPrice().get().multiply(new BigDecimal("1.015")).setScale(3, RoundingMode.HALF_UP);
        if (stock.getZhangTingJia() != null && NumberUtil.isGreaterOrEqual(price, stock.getZhangTingJia()))
            price = stock.getZhangTingJia();
        Integer volume = buyVolume(cash, price);
        log.info("{}({})【同花顺委托买入】{}----资金：{}，价格：{}，股数：{}", userName, userId, stock.codeName(), cash, price, volume);
        if (volume == 0) {
            log.error("{}({})【同花顺委托买入】{}----委托数量为0----资金：{}，价格：{}", userName, userId, stock.codeName(), cash, price);
            return false;
        }
//        String record = cfg.getUserId() + ","   //资金账户
//                + (cfg.getIsCredit() ? "2" : "0") + ","   //账户类型——普通：0    信用：2
//                + stock.getStock_code() + "." + stock.getMarket() + "," //股票代码
//                + "0,"   //委托方向——普通买入：0      普通卖出：1    担保品买入：0    担保品卖出：1        融资买入：6      融券卖出：7    买券还券：9        卖券还款：8       专项买券还券：44      专项融券卖出: 43
//                + volume + ","//数量
//                + ","//价格类型（最新价为3，限价不设置）
//                + ","//浮动价格
//                + ","//浮动范围
//                + price + ","//限定价格
//                + ",,," + "\r\n";//算法类型,最大数量,最小数量,算法参数
        String record = cfg.getUserId() + ","   //资金账户
                + (cfg.getIsCredit() ? "2" : "0") + ","   //账户类型——普通：0    信用：2
                + stock.getStock_code() + "." + stock.getMarket() + "," //股票代码
                + "0,"   //委托方向——普通买入：0      普通卖出：1    担保品买入：0    担保品卖出：1        融资买入：6      融券卖出：7    买券还券：9        卖券还款：8       专项买券还券：44      专项融券卖出: 43
                + volume + ","//数量
                + "3,"//价格类型（最新价为3，限价不设置）
                + stock.currentPrice().get().multiply(new BigDecimal("0.015")).setScale(3, RoundingMode.HALF_UP) + ","//浮动价格
                + ","//浮动范围
                + ","//限定价格
                + ",,," + "\r\n";//算法类型,最大数量,最小数量,算法参数
        FileUtil.writeString("资金账户,账户类型,股票代码,委托方向,数量,价格类型,浮动价格,浮动范围,限定价格,算法类型,最大数量,最小数量,算法参数\r\n" + record,
                FileUtil.touch(LanZiAPathBase + "Trade\\" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv"), "GBK");
        FileUtil.writeString("资金账户,账户类型,股票代码,委托方向,数量,价格类型,浮动价格,浮动范围,限定价格,算法类型,最大数量,最小数量,算法参数\r\n" + record,
                FileUtil.touch(LanZiAPathBase + "Buy\\Tmp\\" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv"), "GBK");
        orders.add(Order.builder().userId(userId).orderType(OrderType.Buy).localID(uuid)
                .code(stock.getStock_code()).price(price).volume(BigDecimal.valueOf(volume)).amount(price.multiply(BigDecimal.valueOf(volume)))
                .status(OrderStatus.Submitted).orderTime(new Date())
                .build());
        return true;
    }

    private Integer buyVolume(BigDecimal cash, BigDecimal price) {
        Integer volume = cash.divide(price.multiply(BigDecimal.valueOf(100)), 2, RoundingMode.HALF_UP).intValue() * 100;
//        log.info("资金：{}，价格：{}，股数：{}", cash, price, volume);
        return volume;
    }

    public Boolean sell(Stock stock) {
        log.info("【同花顺委托卖出】股票：{}----账号：{}({})", stock.codeName(), userName, userId);
        String uuid = IdUtil.fastSimpleUUID();

        if (!stock.currentPrice().isPresent()) {
            log.error("没有" + stock.getStock_code() + "的价格");
            return false;
        }
        if (positions == null) queryPositions();

        Optional<Position> op = positions.stream().filter(p -> p.getCode().equals(stock.getStock_code())).findFirst();
        if (!op.isPresent()) {
            log.warn("{}({})【同花顺委托买入】{}----未查到持仓", userName, userId, stock.codeName());
            return false;
        }

        BigDecimal price = stock.currentPrice().get().multiply(new BigDecimal("0.985")).setScale(3, RoundingMode.HALF_UP);
        if (stock.getDieTingJia() != null && NumberUtil.isLessOrEqual(price, stock.getDieTingJia()))
            price = stock.getDieTingJia();
        Integer volume = op.get().getAvailable();
        log.info("{}({})【同花顺委托卖出】{}----价格：{}，股数：{}", userName, userId, stock.codeName(), price, volume);

//        String record = cfg.getUserId() + ","   //资金账户
//                + (cfg.getIsCredit() ? "2" : "0") + ","   //账户类型——普通：0    信用：2
//                + stock.getStock_code() + "." + stock.getMarket() + "," //股票代码
//                + "1,"   //委托方向——普通买入：0      普通卖出：1    担保品买入：0    担保品卖出：1        融资买入：6      融券卖出：7    买券还券：9        卖券还款：8       专项买券还券：44      专项融券卖出: 43
//                + volume + ","//数量
//                + ","//价格类型（最新价为3，现价置空）
//                + ","//浮动价格
//                + ","//浮动范围
//                + price + ","//限定价格
//                + ",,," + "\r\n";//算法类型,最大数量,最小数量,算法参数
        String record = cfg.getUserId() + ","   //资金账户
                + (cfg.getIsCredit() ? "2" : "0") + ","   //账户类型——普通：0    信用：2
                + stock.getStock_code() + "." + stock.getMarket() + "," //股票代码
                + "1,"   //委托方向——普通买入：0      普通卖出：1    担保品买入：0    担保品卖出：1        融资买入：6      融券卖出：7    买券还券：9        卖券还款：8       专项买券还券：44      专项融券卖出: 43
                + volume + ","//数量
                + "3,"//价格类型（最新价为3，现价置空）
                + stock.currentPrice().get().multiply(new BigDecimal("-0.015")).setScale(3, RoundingMode.HALF_UP) + ","//浮动价格
                + ","//浮动范围
                + ","//限定价格
                + ",,," + "\r\n";//算法类型,最大数量,最小数量,算法参数
        FileUtil.writeString("资金账户,账户类型,股票代码,委托方向,数量,价格类型,浮动价格,浮动范围,限定价格,算法类型,最大数量,最小数量,算法参数\r\n" + record,
                FileUtil.touch(LanZiAPathBase + "Trade\\" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv"), "GBK");
        FileUtil.writeString("资金账户,账户类型,股票代码,委托方向,数量,价格类型,浮动价格,浮动范围,限定价格,算法类型,最大数量,最小数量,算法参数\r\n" + record,
                FileUtil.touch(LanZiAPathBase + "Sell\\Tmp\\" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv"), "GBK");
        orders.add(Order.builder().userId(userId).orderType(OrderType.Sell).localID(uuid)
                .code(stock.getStock_code()).price(price).volume(BigDecimal.valueOf(volume)).amount(price.multiply(BigDecimal.valueOf(volume)))
                .status(OrderStatus.Submitted).orderTime(new Date())
                .build());
        return true;
    }

    @Override
    public void buyCancel(Stock stock) {
        log.info("【同花顺撤销买入委托】股票：{}----账号：{}({})", stock.codeName(), userName, userId);
        String uuid = IdUtil.fastSimpleUUID();
        List<String> taskNos = reportDirs.stream().map(path ->
                FileUtil.loopFiles(path, pathname -> pathname.isFile() && pathname.getName().startsWith("委托_") && pathname.getName().endsWith(".csv") && DateUtil.isSameDay(new Date(pathname.lastModified()), new Date()))
        ).flatMap(Collection::stream).map(f -> {
            CsvData data = CsvUtil.getReader(CsvReadConfig.defaultConfig().setContainsHeader(true)).read(f, Charset.forName("gb2312"));
            return data.getRows().stream().filter(r -> r.getByName("账户标识").contains(userId) && r.getByName("方向").equals("0") && r.getByName("股票代码").replaceAll("'", "").equals(stock.getStock_code()))
                    .map(r -> r.getByName("任务ID").trim()).toList();
        }).flatMap(Collection::stream).toList();
        File csv = FileUtil.touch(LanZiAPathBase + "Trade\\撤单" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv");
        FileUtil.writeString("任务ID,挂单ID,委托编号,账户标识\r\n" + String.join(",,,\r\n", taskNos), csv, "GBK");
        FileUtil.writeString("任务ID,挂单ID,委托编号,账户标识\r\n" + String.join(",,,\r\n", taskNos), FileUtil.touch(LanZiAPathBase + "Buy\\Tmp\\撤单" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv"), "GBK");
    }

    @Override
    public void sellCancel(Stock stock) {
        log.info("【同花顺撤销卖出委托】股票：{}----账号：{}({})", stock.codeName(), userName, userId);
        String uuid = IdUtil.fastSimpleUUID();
        List<String> taskNos = reportDirs.stream().map(path ->
                FileUtil.loopFiles(path, pathname -> pathname.isFile() && pathname.getName().startsWith("委托_") && pathname.getName().endsWith(".csv") && DateUtil.isSameDay(new Date(pathname.lastModified()), new Date()))
        ).flatMap(Collection::stream).map(f -> {
            CsvData data = CsvUtil.getReader(CsvReadConfig.defaultConfig().setContainsHeader(true)).read(f, Charset.forName("gb2312"));
            return data.getRows().stream().filter(r -> r.getByName("账户标识").contains(userId) && r.getByName("方向").equals("1") && r.getByName("股票代码").replaceAll("'", "").equals(stock.getStock_code()))
                    .map(r -> r.getByName("任务ID").trim()).toList();
        }).flatMap(Collection::stream).toList();
        File csv = FileUtil.touch(LanZiAPathBase + "Trade\\撤单" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv");
        FileUtil.writeString("任务ID,挂单ID,委托编号,账户标识\r\n" + String.join(",,,\r\n", taskNos), csv, "GBK");
        FileUtil.writeString("任务ID,挂单ID,委托编号,账户标识\r\n" + String.join(",,,\r\n", taskNos), FileUtil.touch(LanZiAPathBase + "Buy\\Tmp\\撤单" + DateUtil.today() + "_" + userId + "_" + stock.getStock_code() + "_" + uuid + ".csv"), "GBK");
    }

    public static void main(String[] args) throws IOException, InterruptedException {
//        List<File> files = FileUtil.loopFiles("D:\\Tools\\ThsQstudio\\log\\algo_file\\report", pathname -> pathname.isFile() && pathname.getName().startsWith("委托_") && pathname.getName().endsWith(".csv") && DateUtil.isSameDay(new Date(pathname.lastModified()), new Date()));

        TradeTHSCfg mn1 = TradeTHSCfg.builder().userName("模拟账号1W").userId("95821496").password("c8s2xhksEf").isCredit(false).maxBuyCntPerDay(1).buyStrategy("shangbang_buy").sellStrategy("fall_avg").company("模拟").build();
        TradeTHSCfg mn5 = TradeTHSCfg.builder().userName("模拟账号5W").userId("95821491").password("f5luPQgXiO").isCredit(false).maxBuyCntPerDay(5).buyStrategy("shangbang_buy").sellStrategy("fall_avg").company("模拟").build();
        TradeTHSCfg mn500 = TradeTHSCfg.builder().userName("模拟账号500W").userId("95821488").password("7BwRYoAjnm").isCredit(false).maxBuyCntPerDay(10).buyStrategy("shangbang_buy").sellStrategy("fall_avg").company("模拟").build();
        Factor f = new Factor();
        f.setOthers(Others.builder().tradeConfig(
                TradeConfig.builder().tradeTHSCfgList(List.of(mn1, mn5, mn500)).build()
        ).build());
        System.out.println(new Gson().toJson(f.getOthers()));
        MainSelect.factor = f;
        accounts = f.getOthers().getTradeConfig().getTradeTHSCfgList().stream().map(StockTradeTHSLanZi::new).collect(Collectors.toConcurrentMap(StockTradeTHSLanZi::getUserId, each -> each, (value1, value2) -> value1));
        List<Stock> stocks = new StockList().mergeThs();
        Stock stock = stocks.stream().filter(s -> s.getStock_code().equals("002351")).findFirst().get();

        accounts.values().forEach(t -> {
            t.buyCancel(stock);
            System.out.println(t.queryFund());
            System.out.println(t.buy(stock));
            System.out.println(t.queryOrder());
        });

        Thread.sleep(2000);

    }
}
